package ch.ethz.fcl.metrobuzz.render.techniques.colors;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureLoader;
import org.newdawn.slick.util.ResourceLoader;

import ch.ethz.fcl.metrobuzz.MBContainer;
import ch.ethz.fcl.metrobuzz.data.texture.MBTexture;
import ch.ethz.fcl.metrobuzz.render.techniques.projection.Projection;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Rectangle;
import ch.ethz.fcl.metrofont.Pen;

/**
 * Display colors map with OpenGL context
 * 
 * @author Zeng Wei
 * @email zeng@arch.ethz.ch
 * 
 */
public class MBColorMap {
	// width 60 pixels, height 464 pixels
	private final String DEFAULT_COLOR_FILE = "res/textures/color_power_of_2.png";
	private final int DEFAULT_WIDTH = 60, DEFAULT_HEIGHT = 464;

	private float widthRatio = 1, heightRatio = 1;
	private String colorFile = null;
	private Texture colorTex = null;
	private BufferedImage colorImage = null;

	// labels and values for color map
	private String legend = null;
	private String[] labels = null;
	private float[] values = null;

	public MBColorMap() {
		colorFile = DEFAULT_COLOR_FILE;
		labels = new String[] { "0", "100" };
		values = new float[] { 0, 100 };

		widthRatio = DEFAULT_WIDTH / 64.0f;
		heightRatio = DEFAULT_HEIGHT / 512.0f;

		loadImage();
	}

	public MBColorMap(String colorFile, String legend, String[] labels,
			float[] values) {
		if (colorFile == null) {
			this.colorFile = DEFAULT_COLOR_FILE;
			widthRatio = DEFAULT_WIDTH / 64.0f;
			heightRatio = DEFAULT_HEIGHT / 512.0f;
		} else
			this.colorFile = colorFile;

		this.legend = legend;
		this.labels = labels;
		this.values = values;

		loadImage();

		if (colorFile != null
				&& colorFile.equals("res/textures/color2_power_of_2.png")) {
			widthRatio = 50.0f / 64.0f;
			heightRatio = 259.0f / 512.0f;
		}

		if (colorFile != null
				&& colorFile.equals("res/textures/grey_color_map.png")) {
			widthRatio = 1;
			heightRatio = 1;
		}

		if (colorFile != null
				&& colorFile.equals("res/textures/color3_power_of_2.png")) {
			widthRatio = 51.0f / 64;
			heightRatio = 464.0f / 512.0f;
		}
	}

	private void loadImage() {
		try {
			colorTex = TextureLoader.getTexture("PNG",
					ResourceLoader.getResourceAsStream(colorFile));

			colorImage = ImageIO.read(new File(colorFile));

			GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
			GL11.glDisable(GL11.GL_TEXTURE_2D);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void render(MBContainer container, float minX, float minY,
			float maxX, float maxY, int rotate) {
		float worldFontSize = container.getViewer().getProjection()
				.getWorldDistance(20);

		float height = maxY - minY + worldFontSize / 10 + worldFontSize;
		if (legend != null)
			height += worldFontSize / 10 + worldFontSize;
		float maxXShift = worldFontSize * 2 / 5
				* (labels[labels.length - 1].length() - 1);
		if (labels[labels.length - 1].length() == 1)
			maxXShift = worldFontSize / 2;
		renderShadowBackground(container, minX - worldFontSize / 2, minY
				- worldFontSize / 5, maxX + maxXShift, minY + height
				+ worldFontSize / 5);

		renderColorTexture(container, minX, minY, maxX, maxY, rotate);
		renderLabels(container, minX, minY, maxX, maxY);

		if (legend != null) {
			GL11.glColor4f(0, 0, 0, 1);
			container.getFont().display(
					legend + "",
					new Pen(minX, maxY + worldFontSize / 5 + worldFontSize,
							worldFontSize));
		}
	}

	private void renderColorTexture(MBContainer container, float minX,
			float minY, float maxX, float maxY, int rotate) {
		GL11.glColor4f(1, 1, 1, 1);
		GL11.glEnable(GL11.GL_TEXTURE_2D);

		GL11.glBindTexture(GL11.GL_TEXTURE_2D, colorTex.getTextureID());
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S,
				GL12.GL_CLAMP_TO_EDGE);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T,
				GL12.GL_CLAMP_TO_EDGE);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
				GL11.GL_LINEAR);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
				GL11.GL_LINEAR);

		Point2D leftDown = new Point2D(minX, minY), rightDown = new Point2D(
				maxX, minY), rightTop = new Point2D(maxX, maxY), leftTop = new Point2D(
				minX, maxY);

		if (rotate == 90) {
			leftDown = new Point2D(maxX, minY);
			rightDown = new Point2D(maxX, maxY);
			rightTop = new Point2D(minX, maxY);
			leftTop = new Point2D(minX, minY);
		} else if (rotate == 180) {
			leftDown = new Point2D(maxX, maxY);
			rightDown = new Point2D(minX, maxY);
			rightTop = new Point2D(minX, minY);
			leftTop = new Point2D(maxX, minY);
		} else if (rotate == 270 || rotate == -90) {
			leftDown = new Point2D(minX, maxY);
			rightDown = new Point2D(minX, minY);
			rightTop = new Point2D(maxX, minY);
			leftTop = new Point2D(maxX, maxY);
		}
		GL11.glBegin(GL11.GL_QUADS);
		GL11.glTexCoord2f(0, 0);
		GL11.glVertex3f(leftTop.getX(), leftTop.getY(), 0);

		GL11.glTexCoord2f(0, heightRatio);
		GL11.glVertex3f(leftDown.getX(), leftDown.getY(), 0);

		GL11.glTexCoord2f(widthRatio, heightRatio);
		GL11.glVertex3f(rightDown.getX(), rightDown.getY(), 0);

		GL11.glTexCoord2f(widthRatio, 0);
		GL11.glVertex3f(rightTop.getX(), rightTop.getY(), 0);
		GL11.glEnd();

		GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
		GL11.glDisable(GL11.GL_TEXTURE_2D);
	}

	private void renderLabels(MBContainer container, float minX, float minY,
			float maxX, float maxY) {
		Projection proj = container.getViewer().getProjection();

		float worldFontSize = proj.getWorldDistance(20);

		float length = maxX - minX;

		GL11.glColor4f(0, 0, 0, 1);

		for (int i = 0; i < values.length; i++) {
			float adjustment = -worldFontSize / 2;

			if (labels[i].length() == 1)
				adjustment = -worldFontSize / 3;
			else
				adjustment = -worldFontSize * 2 / 5 * (labels[i].length() - 1);

			GL11.glColor4f(0, 0, 0, 1);
			container.getFont().display(
					labels[i] + "",
					new Pen(minX + i * length / (labels.length - 1)
							+ adjustment, maxY + worldFontSize / 10,
							worldFontSize));
		}
	}

	protected void renderShadowBackground(MBContainer container, float minX,
			float minY, float maxX, float maxY) {
		Rectangle shadowRegion = new Rectangle(minX, minY, maxX, maxY);
		MBTexture.renderTexture(container.getData().getTexture().getShadow(),
				shadowRegion);
	}

	/**
	 * 
	 * @param value
	 *            input value
	 * @param choice
	 *            1 for linear, 2 for log, 3 for log10
	 * 
	 * @param angle
	 *            90, 180, 270 or -90 for image rotation
	 * @return
	 */
	public Color getColor(float value, int choice, int angle) {
		float colorThre = colorTex.getImageHeight() * heightRatio - 1;
		int colorY = getColorPos(value, choice);

		int rGB = 0;

		if (angle == 0 || angle == 90)
			rGB = colorImage.getRGB(1, colorY);
		else if (angle == 180 || angle == -90 || angle == 270)
			rGB = colorImage.getRGB(1, (int) colorThre - colorY);

		int b = rGB & 0xff; // blue
		int g = ((rGB >>> 8)) & 0xff; // green
		int r = (rGB >>> 16) & 0xff; // red

		return new Color(r, g, b);
	}

	protected int getColorPos(float value, int choice) {
		int rangeIndex = -1;
		if (value > values[values.length - 1])
			rangeIndex = values.length - 1;
		else
			for (int i = 0; i < values.length - 1; i++)
				if (values[i + 1] >= value) {
					rangeIndex = i;
					break;
				}

		// if (value != 0) {
		// float maxValue = values[values.length - 1];
		// if (value < maxValue / 10)
		// value = Math.max(value, maxValue / 10);
		// else if (value < maxValue / 5)
		// value = Math.max(value, maxValue / 5);
		// }
		float colorThre = colorTex.getImageHeight() * heightRatio - 1;
		int colorY = 0;
		if (rangeIndex == values.length - 1)
			colorY = (int) colorThre;
		else if (rangeIndex == -1)
			colorY = 0;
		else {
			float low = values[rangeIndex];
			if (choice == 2 || choice == 3) {
				low = Math.max(low, 1);
				value = Math.max(value, 1);
			}

			float high = values[rangeIndex + 1];

			float colorLow = rangeIndex * colorThre / (values.length - 1);
			float colorHigh = (rangeIndex + 1) * colorThre
					/ (values.length - 1);

			if (choice == 0 || choice == 1)
				colorY = (int) (colorLow + (value - low) / (high - low)
						* (colorHigh - colorLow));
			else if (choice == 2)
				colorY = (int) (colorLow + (Math.log(value) - Math.log(low))
						/ (Math.log(high) - Math.log(low))
						* (colorHigh - colorLow));
			else if (choice == 3)
				colorY = (int) (colorLow + (Math.log10(value) - Math.log10(low))
						/ (Math.log10(high) - Math.log10(low))
						* (colorHigh - colorLow));
			else
				// default assume linear
				colorY = (int) (colorLow + (value - low) / (high - low)
						* (colorHigh - colorLow));
		}

		return Math.min(colorY, (int) colorThre);
	}

	public int getWidth() {
		return colorTex.getImageWidth();
	}

	public int getHeight() {
		return colorTex.getImageHeight();
	}
}
